iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 26
1
Mobile Development

Android TDD 測試驅動開發系列 第 26

Day26 - TDD 測試驅動開發

  • 分享至 

  • xImage
  •  

TDD 測試驅動開發(Test-driven development),是一種「先寫測試再開發程式」的開發技巧。先寫測試除了確保測試程式碼的運作,更有助於在開發初期就先想清楚需求是什麼,先弄清楚程式介面如何設計。

步驟:
1.先寫失敗的測試案例。
2.快速實作功能讓測試案例通過。
3.重構:在不改變功能的前提下,修改程式碼。改善可維護性。

實體書,內容更完整!
Android TDD 測試驅動開發:從 UnitTest、TDD 到 DevOps 實踐

開始TDD

我們用一個加法計算的功能來示範TDD。

先寫測試案例,MyMath.add 傳入1與2,回傳值應為3。
這時候因為還沒有MyMath,可以看到上面顯示紅字。

https://ithelp.ithome.com.tw/upload/images/20191010/201118968wAknKUutN.png

在MyMath上按下Option + Enter,Create class MyMath

https://ithelp.ithome.com.tw/upload/images/20191010/20111896n3JhyFzUD7.png

新增類別MyMath
https://ithelp.ithome.com.tw/upload/images/20191010/20111896cXJ1QCO9ja.png

產生完Class後,回到Testing Code。在add 上按 Option + Enter( Alt + Enter ),Create member function MyMath.add, 產生add方法。

https://ithelp.ithome.com.tw/upload/images/20191010/201118966NbnItvBHo.png

產生add 之後,這時候還不要實作add這個function。

https://ithelp.ithome.com.tw/upload/images/20191010/20111896nCwnxOmvq8.png

回到測試,執行測試。看到 An operation is not implemented。這時完成了TDD的第一步驟,撰寫失敗的測試。

https://ithelp.ithome.com.tw/upload/images/20191010/20111896b7L0IaGBOD.png

接著是TDD的第二步驟,讓ProductionCode 通過測試。這時我們才要完成

https://ithelp.ithome.com.tw/upload/images/20191010/20111896KkwcXJpEmW.png

這樣就完成了TDD的先寫測試,再讓測試通過。先寫測試最主要的好處是讓你先想清楚需求是什麼,依照需求撰寫測試案例,再把Production Code 完成。

再來看一個稍微複雜的案例。在第一單元的賣雨傘的計價範例(晴天打9折)。

第一步:撰寫失敗的測試案例。這個案例描述購買3隻雨傘,1隻100元時,總價應是300

@Test
fun totalPrice(){
    val umbrella = Umbrella()
    val actual = umbrella.totalPrice(3,100)
    val expected = 300
    Assert.assertEquals(expected,actual)
}

透過option + Enter 產生這段尚未實作的Production code

class Umbrella {
    fun totalPrice(quantity: Int, price: Int) :Int {
        TODO("not implemented")
    }
}

執行測試,得到了第一個失敗的測試

An operation is not implemented: not implemented
kotlin.NotImplementedError: An operation is not implemented: not implemented
    at Umbrella.totalPrice(Umbrella.kt:3)

接著讓ProductionCode完成測試。

class Umbrella {
    fun totalPrice(quantity: Int, price: Int) :Int {
        return quantity * price
    }
}

再執行測試就會通過了。

加上晴天打9折的測試案例。購買3份,應是270元。將 IWeather.isSunny()固定回傳晴天。

class UmbrellaTest {

    @Mock
    lateinit var weather: IWeather

    @Before
    fun setup(){
        MockitoAnnotations.initMocks(this)
    }

    @Test
    fun totalPrice_sunnyDay() {
        `when`(weather.isSunny()).thenReturn(true)
        val umbrella = Umbrella(weather)
        val actual = umbrella.totalPrice(3, 100)
        val expected = 270
        Assert.assertEquals(expected, actual)
    }
}

Umbrella的建構子加上了weather

class Umbrella(val weather: IWeather) {
    fun totalPrice(quantity: Int, price: Int) :Int {
        return quantity * price
    }
}

這時候只有IWeather的Interface,並未實作

interface IWeather {
    fun isSunny(): Boolean
}

執行測試,得到失敗的測試。

java.lang.AssertionError: expected:<270> but was:<300>

接著用最簡單的方式完成ProductionCode。加上晴天打9折的判斷。

class Umbrella(val weather: IWeather) {
    fun totalPrice(quantity: Int, price: Int): Int {
        if (weather.isSunny()) {
            return quantity * (price * 0.9).toInt()
        }
        return quantity * price
    }
}

執行測試,通過測試。代表這個需求完成了。
接著TDD的第三步驟:重構程式碼。

fun totalPrice(quantity: Int, price: Int): Int {
    var unitPrice = price

    if (weather.isSunny()) {
        unitPrice = (price * 0.9).toInt()
    }

    return quantity * unitPrice
}

執行全部測試,通過測試後就完成了,也證明了你沒有因為重構而改壞掉。

TDD的先寫測試,其重點在於先想好要的目標,所以這個測試,不只是測試,更是一種需求的描述。以最少量、剛好能運作的測試,不斷的逼出需求的關鍵分歧點。
如果先寫Production code再寫測試,你可能會覺得浪費時間,既然需求都完成了,為什麼還要寫測試。TDD也正好可以解決這個問題。

範例下載:
https://github.com/evanchen76/TDD_DISample

出版書:
Android TDD 測試驅動開發:從 UnitTest、TDD 到 DevOps 實踐

線上課程:
Android 動畫入門到進階
Android UI 進階實戰(Material Design Component)


上一篇
Day25 - Android MVP、MVVM 架構小結
下一篇
Day27 - Android MVP 架構下的 TDD
系列文
Android TDD 測試驅動開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言